The perfect train network

I'm telling you upfront but this blog post is very opinionated. With that out of the way let's start!

I have, since writing this blog post, upgraded my stations slightly to contain better signalling and prevent trains from locking a station. As of writing this edit the first videos of the Space Age expansion came out and I'm really hyped! I will play it once it's out in like 6 days and will also write a blog post about what I think is good or bad once I've gathered enough experience.

At a base a train network should be easily extensible, not too complex that you cannot understand it anymore but complex enough that it allows you to do magic things, it should be quick to build and lastly it should try and prevent dead locks of trains.

Lets start with the first one out of four points

Extensibility

How can we assure that our network is extensible? Well I've thought of two things that you should keep in mind. The first one is that you should have your different parts like straight sections, curves and so on in a blueprint book so they are accessible and always the same. The second one is about placing the blueprints and for that I'll use an example.

Imagine you have a straight rail section going to your coal outpost in the west and now you need to extend it somewhere in the middle to also go to the north for a copper outpost you want to construct. Most of the time you might deconstruct the straight section you want to play your roundabout or intersection at and the place that but what if you roundabout or intersection basically extends the straight rail and builds on top of it? With that you can easily spam your entire blueprint book all over the map without having to deconstruct a single previously built rail section.

Complexity

By complexity I'm talking about how many lanes of rails you have going into the same direction and about your intersections.

I usually choose a one lane rail network as it most of the time is enough to satisfy throughput. This might change with the DLC and 2.0 coming out as new heights can be achieved there and conventional 2 lane blueprints might not be enough anymore.

For how my trains pass by each other I always choose proper intersections over roundabouts as they allow higher throughput of trains as roundabouts. They also imo look much cooler than normal roundabouts.

Quick to build

This somewhat also is related to the extensibility aspect of a rail network but I also mean that each blueprint shouldn't have like dozens of different entities and should be as simple as possible. I tend to usually go for rails, big electric poles and the signals and if I feel really fancy I might add a couple of lamps to the electric poles but there is really nothing else to each blueprint which makes it super fast to build.

Preventing deadlocks

Deadlocks can occur if you have improperly signaled your rails. I have a very strong opinion and I truly believe that in intersections there should be no more than 2 rails crossing each other per signal block to prevent deadlocks in the intersection itself. Obviously this doesn't prevent deadlocks from happening if you have a loop of rails but those are in general really hard to avoid and the best solution is to build large loops. If I have two sections of rail crossing over each other they should have a chain signal in front of the crossing and if I have merging rails I put chain signals before the merge and a normal signal right after it.

For clarification and a visual kick I will add 4 images below that show the individual aspects of what we covered sofar in actual rails.

The straight section is really simple and consists of 3 medium poles, 4 signals and 2 straight rails.

A straight section of rail that contains 4 normal signals in total

A straight section of rail that contains 4 normal signals in total

The curved section is really simple as well and consists of 3 medium poles, 4 signals and 2 curved rails. The inner part of the curve is tighter than the other part to make more space as it is still works out for the intersections later.

A curved section of rail that contains 4 normals signals in total. The curve is a 90 degree curve

A curved section of rail that contains 4 normals signals in total. The curve is a 90 degree curve

The first of two intersection is a three way and isn't much more complex than combining two curves and a straight rail together. The only difference would be the chain signals that were added to make all the intersecting rails be in a block for themselves.

A three way intersection containing a couple of chain signals and is basically 2 curve sections and a straight section combined

A three way intersection containing a couple of chain signals and is basically 2 curve sections and a straight section combined

The second and last intersection is a four way and is a lot more complex. There are a lot more chain signals as there are a lot more intersecting rails. It still is readable tho and not too complex. My favorite part is the fact that it's fully rotation symmetric.

A full four way intersection containing many chain signals and basically consists of 4 curved sections and 2 straight sections

A full four way intersection containing many chain signals and basically consists of 4 curved sections and 2 straight sections

The previously discussed topics obviously only really includes the normal rail sections but nothing about the stations.

The stations

The stations in 2.0 will change to a point where my current stations are barely holding up. For additional context I almost always play with the LTN mod that enables trains to act more like a logistic system similar to the robots and roboports. I will talk about my current setup and what I have in mind for a future setup when 2.0 releases in roughly 2 weeks.

Current setup

My current setup contains 4 different blueprints for stations but they all are made with the same core idea of having double sided trains with a 1-1-1 configuration. For both items and fluids there are 2 stations, one for loading and one for unloading.

Below you can see a straight section with two unloading stations. Both have 12 steel chests for buffering and unload unto 2 full red belts. The unloading stations, as I use LTN, have a bit of combinator logic combined with each station. There are two constant combinators but one is static and doesn't need to be changed. The other one is right next to the buffer chests and you set how many full wagons of items you want to have in the buffer chests.

Two item unloading stations on a straight section of rail

Two item unloading stations on a straight section of rail

Now here is a straight section with two loading stations. Both have 12 steel chests for buffering and can load from 2 full red belts. The loading stations read the contents of the buffering chests and transfer to value to the LTN train station. There's a minimum stack size of 40 total stacks for this station to become available to the network.

Two item loading stations on a straight section of rail

Two item loading stations on a straight section of rail

You can see that the unloading and loading stations are really similar to each other and that's on purpose as they basically serve the save process but in reverse.

There are also unloading and loading stations for fluids. As I really like the fluid tanks of Krastorio 2 for there 3 adjacent inputs on each side I chose to use a mod that adds those tanks. The unloading of fluids with this setup is extremely quick and efficient.

Two fluid unloading stations on a straight section of rail

Two fluid unloading stations on a straight section of rail

The loading stations for fluids aren't much different but have the pumps reversed and don't have a selector combinator for the fluid.

Two fluid loading stations on a straight section of rail

Two fluid loading stations on a straight section of rail

It's also good to know that you can stack four item stations with a gap of two tiles. You can also stack up to five per straight segment but due to the current rails one signal on that side will be deactivated.

Four item unloading stations on a straight section of rail

Four item unloading stations on a straight section of rail

Real world example

I didn't make these rails I also actively use them in my latest vanilla-ish play through that I started to bridge time between now and Space Age. As a small treat I captured an image of the map view. Keep in mind that this base currently only makes red, green and blue science and doesn't have much else going on.

A factory using the rails discussed in this blog post

A factory using the rails discussed in this blog post

As you can see if you look closer some signals far behind the train are still red and there are ways to improve this but for this base it's enough as of now. I will have to create a new rail blueprint book anyways once the 2.0 update releases as the rails are entirely different and I'm really hyped for better signaling and nicer train stations.

Future stations

The full scope of the new stations isn't know to the public yet but as far as I know I assume some things and I will talk about the implications.

A fat warning to all math haters there is math ahead.

The new trains will be rather similar to LTN but not exactly the same. LTN currently works with depots where all the trains wait until LTN assigns them a job where they go pick up some item from a station and drop it off to a requesting station. The 2.0 trains will all go to the same station for example called Input and then have interrupts that tell the train to go to a specific station depending on the item in cargo. This is very useful as you don't need a massive depot for all your trains. Interrupts can also be configured to tell the train to go to a fueling station if fuel gets under a certain threshold. Stations also get a refresh as they now copy all the additional settings like color and train limit if you change it's name to an existing station to that existing trains settings. There will also be a new priority stat which will become really handy imo.

LTN automatically rotates through the stations with the same name so that all get a chance to unload or load and that is really useful but not perfect. I thought about two things that we can do with the new priority stat but they will be tricky to make perfect. Assuming we can set the priority with combinators we can define a system that increases the priority the longer the station hasn't been served. This has a flaw tho.

Imagine a station that unloads iron for a green circuit factory that has been inactive due to low demand for the last couple of hours. Now the fashion but it doesn't have to be served as the station probably has a priority of that station rises and rises and gets served in a round robin really high stockpile of iron plates already. You may already know what I want to talk about but to clarify we can also take into account the items inside the buffer to calculate the priority. This will have to be tested with real world examples but I will take out my LaTeX skills and bust out some formula.

Priorityrequester=Tickselapsed60(1BufferamountBuffermax)Priority_{requester} = \dfrac{Ticks_{elapsed}}{60} * (1 - \dfrac{Buffer_{amount}}{Buffer_{max}})

In this formula TickselapsedTicks_{elapsed} refers to the ticks elapsed since the last service, BuffermaxBuffer_{max} and BufferamountBuffer_{amount} refer to the Buffer capacity and how much is in the buffer respectively. After calculating the priority you can plug that into the station and it should work. Maybe instead of a division of BufferamountBuffer_{amount} and BuffermaxBuffer_{max} you will have to do a logarithmic operation between them so that it rapidly increases and then gets smaller more slowly but again we will have to test this.

A formula using the natural logarithm would look something like this

Priorityrequester=Tickselapsed60(1ln(Bufferamount)ln(Buffermax))Priority_{requester} = \dfrac{Ticks_{elapsed}}{60} * (1 - \dfrac{\ln (Buffer_{amount})}{\ln (Buffer_{max})})

This has other downsides to a linear decrease in priority as even with 1 out of 12 full steel chest of iron ore you get this calculation.

1ln(ChestslotsStackSizeiron ore)ln(12ChestslotsStackSizeiron ore)0.2421 - \dfrac{\ln (Chest_{slots} * StackSize_{iron\ ore})}{\ln (12 * Chest_{slots} * StackSize_{iron\ ore})} \approx 0.242

You can see that the priority already gets multiplied by 0.2420.242 at one full chest which is roughly 1.21.2 cargo wagons. This might be bad but also might be good and we will have to see if we have to do something different entirely. I do believe tho that my general idea of it is robust and should hold up with implemented correctly.

After some testing with the current combinators and extensive reading of the upcoming combinators features I noticed that there is likely no performant way of using any logarithm for this. I also realized that Factorio obviously doesn't have floating point numbers for combinator signals which makes sense as you can't divide an item into 3 pieces for example but fixed point arithmetic should be enough as it doesn't have to be perfectly accurate, good enough. I also thought that it might be not too useful to count seconds but instead count minutes instead.

Priorityrequester=Tickselapsed3600(1000Bufferamount1000Buffermax)Priority_{requester} = \dfrac{Ticks_{elapsed}}{3600} * (1000 - \dfrac{Buffer_{amount} * 1000}{Buffer_{max}})

It isn't much different than the first iteration but we divide TickselapsedTicks_{elapsed} by 36003600  instead of 6060. We also multiply the amount by 10001000 and subtract the result of the division between BufferamountBuffer_{amount} and BuffermaxBuffer_{max} from 10001000  instead of 11. With this we get a number between  10001000 and 00  depending on how full the buffer is. We then multiply both parts together and get a number that increases by the minute and decreases the fuller the buffer gets. Obviously this is only the formula for the requesting station and the provider stations have a different formula.

The providing stations are different in that the fuller the buffer is the higher the priority should be, the time aspect however stays the same. With this in mind we can create a new formula for provider stations.

Priorityprovider=Tickselapsed3600Bufferamount1000BuffermaxPriority_{provider} = \dfrac{Ticks_{elapsed}}{3600} * \dfrac{Buffer_{amount} * 1000}{Buffer_{max}}

It's basically the same formula but without a subtraction on the buffer side of the formula.

If you think about what we created here you may notice tho that we don't take into account the past couple TickselapsedTicks_{elapsed} values which has an effect when the station hasn't been served for a longer period of time and gets served. This results in the priority getting really low compared to what it was a bit ago and that kinda resets the urgent need to supply this station.

With i=0m1xni\sum\limits_{i=0}^{m-1} x_{n-i} we can get the sum mm last items in a vector. The vector I mentioned will be our history containing the past durations between train services. For good results there will have to be tests with different values for mm to decide what gives the best results for servicing stations correctly efficiently and in a balanced manner. The new formula would look something like the following.

Priorityprovider=i=0m1TickHistoryni3600mBufferamount1000BuffermaxPriority_{provider} = \dfrac{\sum\limits_{i=0}^{m-1} TickHistory_{n-i}}{3600 * m} * \dfrac{Buffer_{amount} * 1000}{Buffer_{max}}

Now this looks like a cool formula! There is something tho that bugs me. I noticed it being more of a problem in mods like Krastorio 2 and Space Exploration where you tend to have byproducts more often. In Krastorio 2 for example your uranium processing has 4 outputs, the normal two Uranium-238 and Uranium-235 as well as iron ore and stone. Obviously you don't want the byproducts that are useless for that part of the factory to pile up and some day stop the entire processing plant so you would set a priority when using LTN to prefer picking up from this station. We have no such measure in our current formula but it can be as easy as multiplying the output of everything by a fixed number like 10. This would ensure that the calculated priority would be the same with less time waited and a fuller buffer.

Priorityprovider=i=0m1TickHistoryni3600mBufferamount1000BuffermaxPriorityMultiplierPriority_{provider} = \dfrac{\sum\limits_{i=0}^{m-1} TickHistory_{n-i}}{3600 * m} * \dfrac{Buffer_{amount} * 1000}{Buffer_{max}} * PriorityMultiplier

I thought of something that makes this priority calculation somewhat inefficient. If you paid attention so far you will have noticed that we only do multiplications at the top level of the formula meaning if even a single value is 0, our priority is 0 as well. This might not be an issue as the station is not deactivated but considering we use minutes instead of seconds in our formula we will potentially multiply by 0. Thankfully this isn't as big of an issue as it was before where we used the elapsed ticks since the last train but it still is an issue regardless. This is also an issue for the buffer when the buffer is fully empty we end up dividing 0 by some other number which results in 0.

As of now I can't think of anything else that might go wrong so I present to you the final 2 formulas. The first being for requester stations and the second one being for provider stations.

Priorityrequester=max(1,i=0m1TickHistoryni3600m)(1000Bufferamount1000Buffermax)PriorityMultiplierPriority_{requester} = \max(1, \dfrac{\sum \limits_{i=0}^{m-1} TickHistory_{n-i}}{3600 * m}) * (1000 - \dfrac{Buffer_{amount} * 1000}{Buffer_{max}}) * PriorityMultiplierPriorityprovider=max(1,i=0m1TickHistoryni3600m)max(1,Bufferamount1000Buffermax)PriorityMultiplierPriority_{provider} = \max(1, \dfrac{\sum\limits_{i=0}^{m-1} TickHistory_{n-i}}{3600 * m}) * \max(1, \dfrac{Buffer_{amount} * 1000}{Buffer_{max}}) * PriorityMultiplier

These two formulas should be entirely buildable in Factorio. The history will be a bit more complex and might be too big to properly handle but I will do some testing to see how they can be implemented.